-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[APM] Add UI Indices runtime configuration #48079
[APM] Add UI Indices runtime configuration #48079
Conversation
💔 Build Failed
|
@ogupte Would it be possible to rename all things "UI indices" to simply "Indices". So in the sidenav and page title. |
x-pack/legacy/plugins/apm/index.ts
Outdated
@@ -44,7 +44,7 @@ export const apm: LegacyPluginInitializer = kibana => { | |||
// TODO: rename to apm_oss.indexPatternTitle in 7.0 (breaking change) | |||
apmIndexPatternTitle: config.get('apm_oss.indexPattern'), | |||
apmServiceMapEnabled: config.get('xpack.apm.serviceMapEnabled'), | |||
apmTransactionIndices: config.get('apm_oss.transactionIndices') | |||
apmTransactionIndices: config.get('apm_oss.transactionIndices') // TODO address this! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the problem that injectDefaultVars
has to return synchronously?
Or could you fetch the saved object here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do believe it has to be synchronous, but regardless: the issue here is that the value of apm_oss.transactionIndices
could have changed via the UI Indices settings UI since it was injected here during plugin startup. So I'll have to make an API call for it whenever the client requires this index.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh yes ofc. Instead of making a separate endpoint for this, can you call the existing endpoint that exposes the ui indices?
const [uiIndices, setUiIndices] = useState<{ [key: string]: string }>({}); | ||
const [isSaving, setIsSaving] = useState(false); | ||
|
||
const callApmApiFromHook = useCallApmApi(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it necessary to say it comes from a hook? I got a little confused with that name at first.
const callApmApiFromHook = useCallApmApi(); | |
const callApmApi = useCallApmApi(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Naming conflicts with the function argument to useFetcher
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about using calApmApi
from the hook everywhere?
const callApmApi = useCallApmApi();
const { data = [], status, refetch } = useFetcher(
() => callApmApi({ pathname: `/api/apm/settings/ui-indices` }),
[callApmApi]
);
I think it's confusing having two different instances of it.
<EuiSpacer /> | ||
<EuiFlexGroup justifyContent="flexEnd"> | ||
<EuiFlexItem grow={false}> | ||
<EuiButton onClick={refetch}>Cancel</EuiButton> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why refetch
on Cancel?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cancel behavior resets the values to whatever was previously saved, which can be accomplished by calling refetch
. Alternatively i could add more to the component state to store the initial values, and set that when cancel is clicked.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, makes sense. Seems simpler to just refetch 👍
const { data = [], refetch } = useFetcher( | ||
callApmApi => callApmApi({ pathname: `/api/apm/settings/ui-indices` }), | ||
[], | ||
{ preservePreviousData: false } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why preservePreviousData: false
?
{ configuration: 'apm_oss.sourcemapIndices', label: 'Sourcemap Indices' }, | ||
{ configuration: 'apm_oss.errorIndices', label: 'Error Indices' }, | ||
{ configuration: 'apm_oss.onboardingIndices', label: 'Onboarding Indices' }, | ||
{ configuration: 'apm_oss.spanIndices', label: 'Span Indices' }, | ||
{ configuration: 'apm_oss.transactionIndices', label: 'Transaction Indices' }, | ||
{ configuration: 'apm_oss.metricsIndices', label: 'Metrics Indices' }, | ||
{ | ||
configuration: 'apm_oss.apmAgentConfigurationIndex', | ||
label: 'Agent Configuration Index' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do these need translations?
const [uiIndices, setUiIndices] = useState<{ [key: string]: string }>({}); | ||
const [isSaving, setIsSaving] = useState(false); | ||
|
||
const callApmApiFromHook = useCallApmApi(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Naming conflicts with the function argument to useFetcher
.
); | ||
|
||
useEffect(() => { | ||
setUiIndices( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I think it's generally more robust to initially create an empty object for the form values, and then merge them synchronously inside of the render
function. This explicitly prioritizes user changes over network data, and can help eliminate situations where the user will lose data due to a race condition
<EuiSpacer size="m" /> | ||
<EuiText size="s" grow={false}> | ||
<p> | ||
The APM UI uses index patterns to query your APM indices. If |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Translation?
import { APMClient } from '../../../../services/rest/createCallApmApi'; | ||
|
||
const INDICES = [ | ||
{ configuration: 'apm_oss.sourcemapIndices', label: 'Sourcemap Indices' }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Further down, I thought configuration
was an object, but it is a string, so maybe something like configurationName
would be helpful.
}; | ||
} | ||
|
||
function flatValues<T = any>(obj: Record<string, any>, deepValues = []): T[] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm having a bit of a hard time understanding what this function does. I guess it's getting all the values from apm_oss
? Would it be better to just explicitly define that here? Seems like deepValues
is not being used either? e.g.:
return Object.values(apmIndices.apm_oss);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the purpose of flatValues
as opposed to Object.values(apmIndices.apm_oss);
is to keep getApmIndicesList
generic. If we add a new index configuration, e.g. xpack.apm.ui.someNewIndex
, then it gets included in getApmIndicesList
without having to change that function. I open to changing this, but that was my original motivation.
const APM_UI_INDICES = [ | ||
'apm_oss.sourcemapIndices', | ||
'apm_oss.errorIndices', | ||
'apm_oss.onboardingIndices', | ||
'apm_oss.spanIndices', | ||
'apm_oss.transactionIndices', | ||
'apm_oss.metricsIndices', | ||
'apm_oss.apmAgentConfigurationIndex' | ||
]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
perhaps an enum
would limit the amount of times we have to (re)define this?
throw err; | ||
} | ||
|
||
return APM_UI_INDICES.map(indexConfig => ({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to use this function for getApmIndices
as well? Or are there subtle differences that I'm missing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the difference is that getApmIndices returns the resolved static/runtime indices, while this function returns more of a breakdown of the resolution, which informs the UI how to render the settings page. i.e. getApmIndices
could be derived from the result of this function, not the other way around.
const savedObjectsClient = getSavedObjectsClient(server, 'data'); | ||
try { | ||
await savedObjectsClient.create('apm-ui-indices', uiIndicesSavedObject, { | ||
id: 'apm-ui-indices', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've seen this a couple of times now as well, should we use a constant for this?
overwrite: true | ||
}); | ||
} catch (err) { | ||
server.log('error', err); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
out of curiosity, should we always intercept server errors and log them explicitly, or does that happen automatically?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your right, it's not necessary here. I'll remove these.
💔 Build Failed
|
ef64486
to
5e191ed
Compare
💔 Build Failed
|
💔 Build Failed
|
export async function getApmIndicesList(server: Server) { | ||
const apmIndices = await getApmIndices(server); | ||
const set = new Set<string>(Object.values(apmIndices.apm_oss)); // ensure unique values | ||
return Array.from(set); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not following this. Why would getApmIndices not return unique values? Or rather: is it important whether they are unique?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh, now I see. This is specifically for addFilterForLegacyData
? I found getApmIndicesList
a little confusing. Perhaps you can call Object.values(await getApmIndices(server))
from getParamsForSearchRequest
and delete getApmIndicesList
because I don't see the necessity for removing duplicates.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is both getApmIndicesList
and listUiIndices
. That seems a little ambiguous.
try { | ||
const apmUiIndicesSavedObject = await getApmUiIndicesSavedObject(server); | ||
const apmUiIndicesConfig = getApmUiIndicesConfig(server.config()); | ||
return merge({}, apmUiIndicesConfig, apmUiIndicesSavedObject); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the nice thing about having identical structures in the config and saved object. It's straightforward to merge them 👍
const APM_UI_INDICES_SAVED_OBJECT_ID = 'apm-ui-indices'; | ||
|
||
export async function getApmUiIndicesSavedObject(server: Server) { | ||
const savedObjectsClient = getSavedObjectsClient(server, 'data'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I forget: did we settle on whether runtime index configuration should be per user, or across all users? And whether anyone should be able to change it or only admins?
'apmTransactionIndices' | ||
); | ||
const callApmApi: APMClient = createCallApmApi(http); | ||
const indexPatternName = await callApmApi({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const indexPatternName = await callApmApi({ | |
const transactionIndices = await callApmApi({ |
const { server } = core.http; | ||
const setup = await setupRequest(req); | ||
const { indexConfigurationName } = path; | ||
return await getUiIndex({ setup, server, indexConfigurationName }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of filtering on the backend, what about simply returning all config options:
return await getApmIndices(server);
It's not a big response, so the savings ar negligible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This way you can get rid of get_ui_index.ts
@@ -0,0 +1,90 @@ | |||
/* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this file be located in lib/settings/ui_indices
?
(acc, key) => set(acc, key, uiIndices[key]), | ||
{ apm_oss: {} } | ||
); | ||
return await storeApmUiIndicesSavedObject(server, uiIndicesSavedObject); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is the reducer
above necessary? Currently only keys in apm_oss
is supported anyway. So wouldn't this work?
return await storeApmUiIndicesSavedObject(server, uiIndicesSavedObject); | |
return await storeApmUiIndicesSavedObject(server, uiIndices); |
} | ||
|
||
interface ApmUiIndicesSavedObject { | ||
[key: string]: any; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this only necessary to satisfy the savedObjectsClient.get
interface?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
correct, it satisfies the SavedObjectAttributes
type
@@ -31,12 +32,17 @@ export async function setupRequest(req: Legacy.Request) { | |||
const query = (req.query as unknown) as APMRequestQuery; | |||
const { server } = req; | |||
const config = server.config(); | |||
const [uiFiltersES, indices] = await Promise.all([ | |||
decodeUiFilters(server, query.uiFilters), | |||
getApmIndices(server) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently getApmIndices
is called twice for each request. First to get the list of APM indices, and afterwards to verify whether it is an APM index that was queries or not.
What do you think about caching getApmIndices
for a short amount of time, eg. 5 seconds? That way if we make several concurrent requests hopefully they won't all have to call getApmIndices
. At worst they'll only do it once per request.
); | ||
} | ||
return indexConfigurationValue; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned elsewhere I don't think this method is necessary.
"apm_oss": { | ||
"properties": { | ||
"sourcemapIndices": { | ||
"type": "text" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
text
is mostly used for full-text searches. It will go through the analyzer, will be tokenized and such.
Unless we want that we should use keyword
@@ -0,0 +1,61 @@ | |||
/* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about moving this to x-pack/legacy/plugins/apm/server/routes/settings/ui_indices.ts
.
Similarly x-pack/legacy/plugins/apm/server/routes/settings.ts
should be moved to x-pack/legacy/plugins/apm/server/routes/settings/agent_configuration.ts
84bf62b
to
6262e63
Compare
💔 Build Failed
|
6262e63
to
d0421ac
Compare
💔 Build Failed
|
} | ||
"apm_oss.sourcemapIndices": { | ||
"type": "keyword" | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like a good move to flatten the option keys 👍
}: { | ||
callApmApi: APMClient; | ||
uiIndices: StringMap<string>; | ||
apmIndices: StringMap<string>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for doing this everywhere. Tedious work but much more consistent now 👍
client, | ||
indices: { apm_oss } | ||
} = setup; | ||
const { start, end, uiFiltersES, client, indices } = setup; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice to be back on a single line 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm!
Couple of TODOs
- decide whether it should be per person or for all
- decide whether specific permissions are required
- possible caching so each request doesn't fetch the saved object twice
I'm okay with merging as-is and then tackle the above questions in follow-up PRs
config.get<string>('apm_oss.transactionIndices') | ||
indices['apm_oss.metricsIndices'], | ||
indices['apm_oss.errorIndices'], | ||
indices['apm_oss.transactionIndices'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Each config options can contain a comma separated list of several options. This is mostly used for CCS like apm-*, *:apm-*
:
index: [
'apm-*-transaction-*, *:apm-*-transaction-*',
'apm-*-span-*, *:apm-*-span-*',
'apm-*-error-*, *:apm-*-error-*'
]
Can you make sure that queries work with comma-separated strings?
- index configuration in settings page - defaults to kibana.yml configuration values
.apm-agent-configuration index which caused failures in those APIs
d0421ac
to
0409cd2
Compare
💚 Build Succeeded |
* Add UI Indices runtime configuration - index configuration in settings page - defaults to kibana.yml configuration values * fix tests * Code review feedback and cleanup * fix i18n * Code review feedback * Address code review feedback. * Fixes bug where legacy data filter was including the .apm-agent-configuration index which caused failures in those APIs
* Add UI Indices runtime configuration - index configuration in settings page - defaults to kibana.yml configuration values * fix tests * Code review feedback and cleanup * fix i18n * Code review feedback * Address code review feedback. * Fixes bug where legacy data filter was including the .apm-agent-configuration index which caused failures in those APIs
Great to see this merged. Good job @ogupte ! Will you create the follow-up issues regarding permission model? |
Test: |
Closes #45247
Adds UI Indices runtime configuration